package com.sgx.learning.luban.homework;


import com.sgx.learning.luban.homework.attributes.AttributeHandlerUtils;
import com.sgx.learning.luban.homework.attributes.AttributeInfo;
import com.sgx.learning.luban.homework.basictype.U1;
import com.sgx.learning.luban.homework.basictype.U2;
import com.sgx.learning.luban.homework.basictype.U4;
import com.sgx.learning.luban.homework.constantpool.*;
import com.sgx.learning.luban.homework.fieldinfo.FieldInfo;
import com.sgx.learning.luban.homework.methodsinfo.MethodInfo;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;

import static com.sgx.learning.luban.homework.constantpool.ConstantPoolInfo.*;


public class ClassRead {

    public static ClassFile read(String filePath) throws IOException {
        File file = new File(filePath);
        if(!file.exists() || file.isDirectory()) {
            throw new IOException("文件不存在或文件格式错误");
        }
        FileInputStream is = new FileInputStream(file);
        ClassFile classFile = new ClassFile();
        classFile.setMagic(U4.read(is));
        classFile.setMinorVersion(U2.read(is));
        classFile.setMajorVersion(U2.read(is));
        classFile.setConstantPoolCount(U2.read(is));
        classFile.setConstantPools(readConstantPool(is,classFile.getConstantPoolCount()));
        classFile.setAccessFlags(U2.read(is));
        classFile.setThisClass(U2.read(is));
        classFile.setSuperClass(U2.read(is));
        classFile.setInterfacesCount(U2.read(is));
        classFile.setInterfaces(readInterfaces(is,classFile.getInterfacesCount()));
        classFile.setFieldCount(U2.read(is));
        classFile.setFieldInfos(readFields(is,classFile.getFieldCount(),classFile.getConstantPools()));
        classFile.setMethodsCount(U2.read(is));
        classFile.setMethodsInfo(readMethodInfo(is,classFile.getMethodsCount(),classFile.getConstantPools()));
        classFile.setAttributesCount(U2.read(is));
        classFile.setAttributes(readAttributes(is,classFile.getAttributesCount(),classFile.getConstantPools()));

        //额外扩展信息
        classFile.setBasicFileAttributes(Files.readAttributes(Paths.get(filePath), BasicFileAttributes.class));
        classFile.setPath(filePath);
        return classFile;
    }

    private static AttributeInfo[] readAttributes(InputStream is, U2 attributesCount, ConstantPoolInfo[] constantPools) throws IOException {
        return AttributeHandlerUtils.getAttribute(attributesCount.getValue(),is,constantPools);
    }

    private static MethodInfo[] readMethodInfo(InputStream is, U2 methodsCount, ConstantPoolInfo[] constantPools) throws IOException {
        MethodInfo[] methodInfos = new MethodInfo[methodsCount.getValue()];
        for (int i = 0; i < methodsCount.getValue(); i++) {
            MethodInfo temp = new MethodInfo();
            temp.read(is,constantPools);
            methodInfos[i] = temp;
        }
        return methodInfos;
    }

    private static FieldInfo[] readFields(InputStream is, U2 fieldCount, ConstantPoolInfo[] constantPools) throws IOException {
        FieldInfo[] fieldsInfos = new FieldInfo[fieldCount.getValue()];
        for (int i = 0; i < fieldCount.getValue(); i++) {
            FieldInfo temp = new FieldInfo();
            temp.read(is,constantPools);
            fieldsInfos[i] = temp;
        }
        return fieldsInfos;
    }

    private static ConstantClassInfo[] readInterfaces(InputStream is, U2 interfacesCount) throws IOException {
        ConstantClassInfo[] constantClassInfos = new ConstantClassInfo[interfacesCount.getValue()];
        for (int i = 0; i < interfacesCount.getValue(); i++) {
            ConstantClassInfo temp = new ConstantClassInfo();
            temp.read(is);
            constantClassInfos[i] = temp;
        }
        return constantClassInfos;
    }

    public static ConstantPoolInfo[] readConstantPool(InputStream is, U2 constantCount) throws IOException {
        ConstantPoolInfo[] constantPools = new ConstantPoolInfo[constantCount.getValue() - 1];
        for (int i = 0; i < constantCount.getValue() - 1; i++) {
            U1 tag = U1.read(is);
            ConstantPoolInfo temp;
            switch (tag.getValue()) {
                case TAG_C_UTF8_INFO:{
                    temp = new ConstantUtf8Info();
                    temp.read(is);
                }break;
                case TAG_C_INTEGER_INFO:{
                    temp = new ConstantIntegerInfo();
                    temp.read(is);
                }break;
                case TAG_C_FLOAT_INFO:{
                    temp = new ConstantFloatInfo();
                    temp.read(is);
                }break;
                case TAG_C_LONG_INFO:{
                    temp = new ConstantLongInfo();
                    temp.read(is);
                }break;
                case TAG_C_DOUBLE_INFO:{
                    temp = new ConstantDoubleInfo();
                    temp.read(is);
                }break;
                case TAG_C_CLASS_INFO:{
                    temp = new ConstantClassInfo();
                    temp.read(is);
                }break;
                case TAG_C_STRING_INFO:{
                    temp = new ConstantStringInfo();
                    temp.read(is);
                }break;
                case TAG_C_FIELDREF_INFO:{
                    temp = new ConstantFieldrefInfo();
                    temp.read(is);
                }break;
                case TAG_C_METHODREF_INFO:{
                    temp = new ConstantMethodrefInfo();
                    temp.read(is);
                }break;
                case TAG_C_INTERFACEMETHODREF_INFO:{
                    temp = new ConstantInterfaceMethodrefInfo();
                    temp.read(is);
                }break;
                case TAG_C_NAMEANDTYPE_INFO:{
                    temp = new ConstantNameAndTypeInfo();
                    temp.read(is);
                }break;
                default:throw new IOException("解析错误：未识别的常量池类型:" + tag.getValue());
            }
            temp.setTag(tag.getValue());
            constantPools[i] = temp;
        }
        return constantPools;
    }
}
